package com.cy.factory;

import com.cy.anno.Autowired;
import com.cy.anno.Component;
import com.cy.anno.Controller;
import com.cy.anno.Transactional;
import com.cy.pojo.BeanDefinition;
import com.cy.pojo.BeanProperty;
import com.cy.utils.Scanner;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;

public class BeanFactory {

    private static Map<String, Object> singletonObject = new HashMap<>();

    private static List<BeanDefinition> beanDefinitions = new ArrayList<>();

    /**
     * 记录需要代理的bean
     */
    private static List<BeanDefinition> beanProxyDefinitions = new ArrayList<>();

    private String mvnScanPackage;

    private static BeanFactory beanFactory = null;

    public static BeanFactory getInstance(){
        return getInstance(null);
    }

    public static synchronized BeanFactory getInstance(String mvnScanPackage){
        if (beanFactory == null){
            beanFactory = new BeanFactory(mvnScanPackage);
        }
        return beanFactory;
    }

    public BeanFactory(String mvnScanPackage) {
        this.mvnScanPackage = mvnScanPackage;
        initBeanFactory();
    }

    /**
     * 初始化加载，将xml中的配置加载到map中
     */
    private void initBeanFactory() {

        InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");

        String scanPackage = "com.cy";

        try {
            initBeanDefinitionsFromAnnotation(scanPackage);

            initMvcBeanDefinitionsFromAnnotation(mvnScanPackage);

            //将所有xml中的bean装入BeanDefinitions
            initBeanDefinitionsFromXmlStream(inputStream);

            //将所有beanDefinition装入singleton
            initSingleton();

            //将所有需要自动装配的字段都装配进class中
            initAutowried();

            //将需要使用代理类的Bean改为代理类
            initProxyBean();
        } catch (DocumentException | ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 对MVC的Controller类做扫描
     * @throws Exception
     */
    private static void initMvcBeanDefinitionsFromAnnotation(String scanPackage) throws Exception {
        if (scanPackage != null){
            Scanner scanner = new Scanner();
            Set<Class<?>> annotationClasses = scanner.getAnnotationClasses(scanPackage, Controller.class);
            for (Class<?> annotationClass : annotationClasses) {
                Controller controller = annotationClass.getAnnotation(Controller.class);

                BeanDefinition beanDefinition = new BeanDefinition();

                String id = controller.value();
                if ("".equals(id)){
                    id = beanNameGenerator(annotationClass);
                }

                beanDefinition.setId(id);
                beanDefinition.setClazz(annotationClass);

                List<BeanProperty> beanProperties = new ArrayList<>();
                //注入Autowired的类
                List<Field> fields = scanner.getAnnotationFields(annotationClass, Autowired.class);
                for (Field field : fields) {
                    BeanProperty beanProperty = new BeanProperty();
                    //获取字段名为需要注入的字段名
                    beanProperty.setName(field.getName());
                    //获取字段名为注入的bean的查找名称
                    beanProperty.setRef(field.getName());

                    beanProperties.add(beanProperty);
                }
                beanDefinition.setProperties(beanProperties);

                beanDefinitions.add(beanDefinition);
            }
        }
    }

    private static void initProxyBean() {
        for (BeanDefinition beanProxyDefinition : beanProxyDefinitions) {
            //获取需要修改为代理类的类
            Object o = singletonObject.get(beanProxyDefinition.getId());
            //获取代理工厂类
            ProxyFactory proxyFactory = (ProxyFactory) singletonObject.get("proxyFactory");
            //判断是否继承了接口
            boolean assignableFromInvocationHandle = o.getClass().isAssignableFrom(InvocationHandler.class);
            o = assignableFromInvocationHandle?proxyFactory.getJdkProxy(o):proxyFactory.getCglibProxy(o);
            //将代理类替换过去
            singletonObject.put(beanProxyDefinition.getId(), o);
        }
    }

    private static void initAutowried() throws IllegalAccessException, InstantiationException, NoSuchFieldException {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            //获取需要注入的类
            Object o = singletonObject.get(beanDefinition.getId());

            for (BeanProperty property : beanDefinition.getProperties()) {
                //通过反射设置类中的值
                //获取到字段
                Field declaredField = o.getClass().getDeclaredField(property.getName());
                //设置强制访问，防止字段为private访问不到
                declaredField.setAccessible(true);

                //根据配置的ref得到bean
                Object bean = singletonObject.get(property.getRef());
                //把获取到的bean设置到字段中去
                declaredField.set(o, bean);
            }
        }
    }

    private static void initSingleton() throws IllegalAccessException, InstantiationException, NoSuchFieldException {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            //获取id
            String id = beanDefinition.getId();
            //获取到类
            Class<?> clazz = beanDefinition.getClazz();
            //生成实现类
            Object o = clazz.newInstance();

            //最后将该方法放入map中，key是配置的id
            singletonObject.put(id, o);
        }
    }

    private static void initBeanDefinitionsFromAnnotation(String scanPackage) throws Exception {
        Scanner scanner = new Scanner();
        Set<Class<?>> annotationClasses = scanner.getAnnotationClasses(scanPackage, Component.class);
        for (Class<?> annotationClass : annotationClasses) {
            Component component = annotationClass.getAnnotation(Component.class);

            BeanDefinition beanDefinition = new BeanDefinition();

            String id = component.value();
            if ("".equals(id)){
                id = beanNameGenerator(annotationClass);
            }

            beanDefinition.setId(id);
            beanDefinition.setClazz(annotationClass);

            List<BeanProperty> beanProperties = new ArrayList<>();
            //注入Autowired的类
            List<Field> fields = scanner.getAnnotationFields(annotationClass, Autowired.class);
            for (Field field : fields) {
                BeanProperty beanProperty = new BeanProperty();
                //获取字段名为需要注入的字段名
                beanProperty.setName(field.getName());
                //获取字段名为注入的bean的查找名称
                beanProperty.setRef(field.getName());

                beanProperties.add(beanProperty);
            }
            beanDefinition.setProperties(beanProperties);

            //注入Autowired的类
            List<Method> methods = scanner.getAnnotationMethods(annotationClass, Transactional.class);
            if (methods != null && !methods.isEmpty()){
                //如果方法中有注解了Transaction的，那么就使用代理类
                beanDefinition.setUseProxy(true);
                beanProxyDefinitions.add(beanDefinition);
            }

            beanDefinitions.add(beanDefinition);
        }
    }

    /**
     * bean名称生成器
     * @param annotationClass
     * @return
     */
    private static String beanNameGenerator(Class<?> annotationClass) {
        Class<?>[] interfaces = annotationClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            //如果有接口，那么以接口名首字母小写作为bean的名称
            return toLowerCaseFirstOne(anInterface.getSimpleName());
        }
        //否则以类名首字母小写作为bean的名称
        return toLowerCaseFirstOne(annotationClass.getSimpleName());
    }

    /**
     * 首字母转小写
     * @param s
     * @return
     */
    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

    /**
     * 从输入流中获取到所有的BeanDefinition
     * @param inputStream
     * @throws ClassNotFoundException
     * @throws DocumentException
     */
    private static void initBeanDefinitionsFromXmlStream(InputStream inputStream) throws ClassNotFoundException, DocumentException {
        Document document = new SAXReader().read(inputStream);
        //获取根标签 <beans></beans>
        Element rootElement = document.getRootElement();
        List<Element> beansList = rootElement.selectNodes("//bean");

        for (Element element : beansList) {
            //获取id
            String id = element.attributeValue("id");
            //获取实现类全限定名
            String impl = element.attributeValue("impl");

            //根据全限定名，利用反射，生成对象
            //获取到类
            Class<?> clazz = Class.forName(impl);
            List<BeanProperty> beanProperties = new ArrayList<>();
            List<Element> propertyList = element.selectNodes("property");
            for (Element propertyElement : propertyList) {
                BeanProperty beanProperty = new BeanProperty();
                String name = propertyElement.attributeValue("name");
                String ref = propertyElement.attributeValue("ref");

                beanProperty.setName(name);
                beanProperty.setRef(ref);

                beanProperties.add(beanProperty);
            }

            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setId(id);
            beanDefinition.setClazz(clazz);
            beanDefinition.setProperties(beanProperties);

            beanDefinitions.add(beanDefinition);
        }
    }

    /**
     * 根据ID获取到对应的ObjectBeanFactory
     * @param id
     * @return
     */
    public Object getBean(String id){
        return singletonObject.get(id);
    }

    /**
     * Gets the value of singletonObject. *
     *
     * @return the value of singletonObject
     */
    public Map<String, Object> getSingletonObject() {
        return singletonObject;
    }
}
